/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// snd_mix.c -- portable code to mix sounds for snd_dma.c

#include "quakedef.h"

#ifdef _WIN32
#include "winquake.h"
#else
#define DWORD	unsigned long
#endif

#define	PAINTBUFFER_SIZE	2048 //upped from 512 to 2048 - Eradicator
portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE];
int		snd_scaletable[32][256];
int 	*snd_p, snd_linear_count, snd_vol;
short	*snd_out;

void Snd_WriteLinearBlastStereo16 (void)
{
    int		i;
    int		val;

    for (i=0 ; i<snd_linear_count ; i+=2)
    {
        val = (snd_p[i]*snd_vol)>>8;
        if (val > 0x7fff)
            snd_out[i] = 0x7fff;
        else if (val < (short)0x8000)
            snd_out[i] = (short)0x8000;
        else
            snd_out[i] = val;

        val = (snd_p[i+1]*snd_vol)>>8;
        if (val > 0x7fff)
            snd_out[i+1] = 0x7fff;
        else if (val < (short)0x8000)
            snd_out[i+1] = (short)0x8000;
        else
            snd_out[i+1] = val;
    }
}

void S_TransferStereo16 (int endtime)
{
    int		lpos;
    int		lpaintedtime;
    DWORD	*pbuf;
#ifdef _WIN32
    int		reps;
    DWORD	dwSize,dwSize2;
    DWORD	*pbuf2;
    HRESULT	hresult;
#endif

    snd_vol = volume.value*256;

    snd_p = (int *) paintbuffer;
    lpaintedtime = paintedtime;

#ifdef _WIN32
    if (pDSBuf)
    {
        reps = 0;

        while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, &pbuf, &dwSize,
                                               &pbuf2, &dwSize2, 0)) != DS_OK)
        {
            if (hresult != DSERR_BUFFERLOST)
            {
                Con_Printf ("S_TransferStereo16: DS::Lock Sound Buffer Failed\n");
                S_Shutdown ();
                S_Startup ();
                return;
            }

            if (++reps > 10000)
            {
                Con_Printf ("S_TransferStereo16: DS: couldn't restore buffer\n");
                S_Shutdown ();
                S_Startup ();
                return;
            }
        }
    }
    else
#endif
    {
        pbuf = (DWORD *)shm->buffer;
    }

    while (lpaintedtime < endtime)
    {
        // handle recirculating buffer issues
        lpos = lpaintedtime & ((shm->samples>>1)-1);

        snd_out = (short *) pbuf + (lpos<<1);

        snd_linear_count = (shm->samples>>1) - lpos;
        if (lpaintedtime + snd_linear_count > endtime)
            snd_linear_count = endtime - lpaintedtime;

        snd_linear_count <<= 1;

        // write a linear blast of samples
        Snd_WriteLinearBlastStereo16 ();

        snd_p += snd_linear_count;
        lpaintedtime += (snd_linear_count>>1);
    }

#ifdef _WIN32
    if (pDSBuf)
        pDSBuf->lpVtbl->Unlock(pDSBuf, pbuf, dwSize, NULL, 0);
#endif
}

extern cvar_t snd_reversestereo;
void S_TransferPaintBuffer(int endtime) //Changed Heavily - Eradicator
{
    int 	out_idx;
    int 	count;
    int 	out_mask;
    int 	*p;
    int 	step;
    int		val;
    int		snd_vol_mask;
    DWORD	*pbuf;

    if (shm->samplebits == 16 && shm->channels == 2)
    {
        S_TransferStereo16 (endtime);
        return;
    }

    p = (int *) paintbuffer;
    count = (endtime - paintedtime) * shm->channels;
    out_mask = shm->samples - 1;
    out_idx = paintedtime * shm->channels & out_mask;
    step = 3 - shm->channels;
    snd_vol_mask = volume.value*256;

    {
        pbuf = (DWORD *)shm->buffer;
    }

    if (shm->samplebits == 16)
    {
        short *out = (short *) pbuf;

        while (count--)
        {
            val = (*p * snd_vol_mask) >> 8;
            p+= step;
            if (val > 0x7fff)
                val = 0x7fff;
            else if (val < (short)0x8000)
                val = (short)0x8000;
            out[out_idx] = val;
            out_idx = (out_idx + 1) & out_mask;
        }
    }
    else if (shm->samplebits == 8)
    {
        unsigned char *out = (unsigned char *) pbuf;

        while (count--)
        {
            val = (*p * snd_vol_mask) >> 8;
            p+= step;
            if (val > 0x7fff)
                val = 0x7fff;
            else if (val < (short)0x8000)
                val = (short)0x8000;
            out[out_idx] = (val>>8) + 128;
            out_idx = (out_idx + 1) & out_mask;
        }
    }
}


/*
===============================================================================

CHANNEL MIXING

===============================================================================
*/

void SND_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int endtime);
void SND_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int endtime);

void S_PaintChannels(int endtime)
{
    int 	i;
    int 	end;
    channel_t *ch;
    sfxcache_t	*sc;
    int		ltime, count;

    while (paintedtime < endtime)
    {
        // if paintbuffer is smaller than DMA buffer
        end = endtime;
        if (endtime - paintedtime > PAINTBUFFER_SIZE)
            end = paintedtime + PAINTBUFFER_SIZE;

        // clear the paint buffer
        Q_memset(paintbuffer, 0, (end - paintedtime) * sizeof(portable_samplepair_t));

        // paint in the channels.
        ch = channels;
        for (i=0; i<total_channels ; i++, ch++)
        {
            if (!ch->sfx)
                continue;
            if (!ch->leftvol && !ch->rightvol)
                continue;
            sc = S_LoadSound (ch->sfx);
            if (!sc)
                continue;

            ltime = paintedtime;

            while (ltime < end)
            {
                // paint up to end
                if (ch->end < end)
                    count = ch->end - ltime;
                else
                    count = end - ltime;

                if (count > 0)
                {
                    if (sc->width == 1)
                        SND_PaintChannelFrom8(ch, sc, count);
                    else
                        SND_PaintChannelFrom16(ch, sc, count);

                    ltime += count;
                }

                // if at end of loop, restart
                if (ltime >= ch->end)
                {
                    if (sc->loopstart >= 0)
                    {
                        ch->pos = sc->loopstart;
                        ch->end = ltime + sc->length - ch->pos;
                    }
                    else
                    {
                        // channel just stopped
                        ch->sfx = NULL;
                        break;
                    }
                }
            }

        }

        // transfer out according to DMA format
        S_TransferPaintBuffer(end);
        paintedtime = end;
    }
}

void SND_InitScaletable (void)
{
    int		i, j;

    for (i=0 ; i<32 ; i++)
        for (j=0 ; j<256 ; j++)
            snd_scaletable[i][j] = ((signed char)j) * i * 8;
}

void SND_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int count)
{
    int 	        data;
    int		        *lscale, *rscale;
    unsigned char   *sfx;
    int		        i;

    if (ch->leftvol > 255)
        ch->leftvol = 255;
    if (ch->rightvol > 255)
        ch->rightvol = 255;

    lscale = snd_scaletable[ch->leftvol >> 3];
    rscale = snd_scaletable[ch->rightvol >> 3];
    sfx = sc->data + ch->pos;

    for (i=0 ; i<count ; i++)
    {
        data = sfx[i];
        paintbuffer[i].left += lscale[data];
        paintbuffer[i].right += rscale[data];
    }

    ch->pos += count;
}

void SND_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int count)
{
    int data;
    int left, right;
    int leftvol, rightvol;
    signed short *sfx;
    int	i;

    leftvol = ch->leftvol;
    rightvol = ch->rightvol;
    sfx = (signed short *)sc->data + ch->pos;

    for (i=0 ; i<count ; i++)
    {
        data = sfx[i];
        left = (data * leftvol) >> 8;
        right = (data * rightvol) >> 8;
        paintbuffer[i].left += left;
        paintbuffer[i].right += right;
    }

    ch->pos += count;
}

